导航菜单
首页 >  Vue3 中使用富文本编辑器wangEditor支持 Vue3 的富  > Vue3项目中集成最强的tinymce富文本编辑器

Vue3项目中集成最强的tinymce富文本编辑器

前言

tinymce是目前公认的最好的富文本编辑器。本篇文章将详细说明在vue3项目中如何集成tinymce,并将 tinymce封装成组件使用

最终效果

正文1. 安装依赖,

这里我们使用了tinymce5,最新已经到tinymce6了。不同版本之间插件上会有差异

npm install tinymce@5.10.22. 安装汉化包

我们在 vue3 项目的public/resource/ 目录下创建文件夹 tinymce,在tinymce下再创建 langs文件夹用来存放语言汉化包

我们将 这个中文包https://gitee.com/shuiche/tinymce-vue3/blob/master/langs/zh_CN.js下载后放到langs文件夹下

3. 迁移ui皮肤文件

我们在 node_modules/tinymce 中找到 skins 文件夹,将其复制到 public/resource/tinymce/ 里

4. 封装组件

在src/components/ 下新建 Tinymce 文件夹。在 Tinymce/ 文件夹下新建3个文件: helper.js , tinymce.js, Tinymce.vue

内容分别是:

helper.js

// ==========helper.js==========const validEvents = [ 'onActivate', 'onAddUndo', 'onBeforeAddUndo', 'onBeforeExecCommand', 'onBeforeGetContent', 'onBeforeRenderUI', 'onBeforeSetContent', 'onBeforePaste', 'onBlur', 'onChange', 'onClearUndos', 'onClick', 'onContextMenu', 'onCopy', 'onCut', 'onDblclick', 'onDeactivate', 'onDirty', 'onDrag', 'onDragDrop', 'onDragEnd', 'onDragGesture', 'onDragOver', 'onDrop', 'onExecCommand', 'onFocus', 'onFocusIn', 'onFocusOut', 'onGetContent', 'onHide', 'onInit', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onLoadContent', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 'onNodeChange', 'onObjectResizeStart', 'onObjectResized', 'onObjectSelected', 'onPaste', 'onPostProcess', 'onPostRender', 'onPreProcess', 'onProgressState', 'onRedo', 'onRemove', 'onReset', 'onSaveContent', 'onSelectionChange', 'onSetAttrib', 'onSetContent', 'onShow', 'onSubmit', 'onUndo', 'onVisualAid']const isValidKey = (key) => validEvents.indexOf(key) !== -1export const bindHandlers = (initEvent, listeners, editor) => { Object.keys(listeners).filter(isValidKey).forEach((key) => { const handler = listeners[key] if (typeof handler === 'function') {if (key === 'onInit') { handler(initEvent, editor)} else { editor.on(key.substring(2), (e) => handler(e, editor))} }})}

tinymce.js

// ==========tinymce.js==========// Any plugins you want to setting has to be imported// Detail plugins list see https://www.tinymce.com/docs/plugins/// Custom builds see https://www.tinymce.com/download/custom-builds/// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configurationexport const plugins = [ 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textpattern visualblocks visualchars wordcount']export const toolbar = [ 'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link preview anchor pagebreak insertdatetime media forecolor backcolor fullscreen']// ==========Tinymce.vue==========import tinymce from 'tinymce/tinymce'import 'tinymce/themes/silver'import 'tinymce/icons/default/icons'import 'tinymce/plugins/advlist'import 'tinymce/plugins/anchor'import 'tinymce/plugins/autolink'import 'tinymce/plugins/autosave'import 'tinymce/plugins/code'import 'tinymce/plugins/codesample'import 'tinymce/plugins/directionality'import 'tinymce/plugins/fullscreen'import 'tinymce/plugins/hr'import 'tinymce/plugins/insertdatetime'import 'tinymce/plugins/link'import 'tinymce/plugins/lists'import 'tinymce/plugins/media'import 'tinymce/plugins/nonbreaking'import 'tinymce/plugins/noneditable'import 'tinymce/plugins/pagebreak'import 'tinymce/plugins/paste'import 'tinymce/plugins/preview'import 'tinymce/plugins/print'import 'tinymce/plugins/save'import 'tinymce/plugins/searchreplace'import 'tinymce/plugins/spellchecker'import 'tinymce/plugins/tabfocus'import 'tinymce/plugins/template'import 'tinymce/plugins/textpattern'import 'tinymce/plugins/visualblocks'import 'tinymce/plugins/visualchars'import 'tinymce/plugins/wordcount'// import 'tinymce/plugins/table';import { computed, nextTick, ref, unref, watch, onDeactivated, onBeforeUnmount, defineProps, defineEmits, getCurrentInstance } from 'vue'import { toolbar, plugins } from './tinymce'import { buildShortUUID } from '@/utils/uuid'import { bindHandlers } from './helper'import { onMountedOrActivated } from '@/hooks/core/onMountedOrActivated'import { isNumber } from '@/utils/is'const props = defineProps({options: {type: Object,default: () => {}},value: {type: String},toolbar: {type: Array,default: toolbar},plugins: {type: Array,default: plugins},modelValue: {type: String},height: {type: [Number, String],required: false,default: 400},width: {type: [Number, String],required: false,default: 'auto'},showImageUpload: {type: Boolean,default: true}})const emits = defineEmits(['change', 'update:modelValue', 'inited', 'init-error'])const { attrs } = getCurrentInstance()const tinymceId = ref(buildShortUUID('tiny-vue'))const containerWidth = computed(() => {const width = props.widthif (isNumber(width)) {return `${width}px`}return width})const editorRef = ref(null)const fullscreen = ref(false)const elRef = ref(null)const tinymceContent = computed(() => props.modelValue)const initOptions = computed(() => {const { height, options, toolbar, plugins } = propsconst publicPath = '/'return {selector: `#${unref(tinymceId)}`,height,toolbar,menubar: 'file edit insert view format table',plugins,language_url: '/resource/tinymce/langs/zh_CN.js',language: 'zh_CN',branding: false,default_link_target: '_blank',link_title: false,object_resizing: false,auto_focus: true,skin: 'oxide',skin_url: '/resource/tinymce/skins/ui/oxide',content_css: '/resource/tinymce/skins/ui/oxide/content.min.css',...options,setup: (editor) => {editorRef.value = editoreditor.on('init', (e) => initSetup(e))}}})const disabled = computed(() => {const { options } = propsconst getdDisabled = options && Reflect.get(options, 'readonly')const editor = unref(editorRef)if (editor) {editor.setMode(getdDisabled ? 'readonly' : 'design')}return getdDisabled ?? false})watch(() => attrs.disabled,() => {const editor = unref(editorRef)if (!editor) {return}editor.setMode(attrs.disabled ? 'readonly' : 'design')})onMountedOrActivated(() => {if (!initOptions.value.inline) {tinymceId.value = buildShortUUID('tiny-vue')}nextTick(() => {setTimeout(() => {initEditor()}, 30)})})onBeforeUnmount(() => {destory()})onDeactivated(() => {destory()})function destory () {if (tinymce !== null) {// tinymce?.remove?.(unref(initOptions).selector!);}}function initSetup (e) {const editor = unref(editorRef)if (!editor) {return}const value = props.modelValue || ''editor.setContent(value)bindModelHandlers(editor)bindHandlers(e, attrs, unref(editorRef))}function initEditor () {const el = unref(elRef)if (el) {el.style.visibility = ''}tinymce.init(unref(initOptions)).then((editor) => {emits('inited', editor)}).catch((err) => {emits('init-error', err)})}function setValue (editor, val, prevVal) {if (editor &&typeof val === 'string' &&val !== prevVal &&val !== editor.getContent({ format: attrs.outputFormat })) {editor.setContent(val)}}function bindModelHandlers (editor) {const modelEvents = attrs.modelEvents ? attrs.modelEvents : nullconst normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEventswatch(() => props.modelValue,(val, prevVal) => {setValue(editor, val, prevVal)})watch(() => props.value,(val, prevVal) => {setValue(editor, val, prevVal)},{immediate: true})editor.on(normalizedEvents || 'change keyup undo redo', () => {const content = editor.getContent({ format: attrs.outputFormat })emits('update:modelValue', content)emits('change', content)})editor.on('FullscreenStateChanged', (e) => {fullscreen.value = e.state})}function handleImageUploading (name) {const editor = unref(editorRef)if (!editor) {return}editor.execCommand('mceInsertContent', false, getUploadingImgName(name))const content = editor?.getContent() ?? ''setValue(editor, content)}function handleDone (name, url) {const editor = unref(editorRef)if (!editor) {return}const content = editor?.getContent() ?? ''const val = content?.replace(getUploadingImgName(name), ``) ?? ''setValue(editor, val)}function getUploadingImgName (name) {return `[uploading:${name}]`}.prefixCls{position: relative;line-height: normal;}textarea {z-index: -1;visibility: hidden;}

在Tinymce中引入了两个外部函数和一个hook,具体内容是:

// ==== isNumber 函数====const toString = Object.prototype.toStringexport function is (val, type) { return toString.call(val) === `[object ${type}]`}export function isNumber (val) { return is(val, 'Number')}// ==== buildShortUUID 函数====export function buildShortUUID (prefix = '') {const time = Date.now()const random = Math.floor(Math.random() * 1000000000)unique++return prefix + '_' + random + unique + String(time)}// ==== onMountedOrActivated hook====import { nextTick, onMounted, onActivated } from 'vue'export function onMountedOrActivated (hook) { let mounted onMounted(() => {hook()nextTick(() => { mounted = true}) }) onActivated(() => {if (mounted) { hook()} })}使用组件

使用时非常简单,我们用 v-model 和 @change 即可使用。

// ......let content = ref('')function handleChange (item) {console.log('change', item)}后记

如有问题,可以留言沟通

相关推荐: